﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using HtmlAgilityPack;
using Informant.Contacts;
using Informant.Google.Contacts;
using Informant.SmsMessaging;
using Newtonsoft.Json;

namespace Informant.Google.Voice.SmsMessaging.Parsing
{
	public class SmsXmlParser : ISmsXmlParser
	{
		#region Fields

		// If userIsOwnerFlag changes be sure to update GetUserIsOwnerFlagAsPhoneNumber()
		private static readonly String userIsOwnerFlag = "\n    Me:\n  ";

		#endregion

		#region ISmsXMLParser Members

		/// <summary>
		/// Parses XML to Sms.
		/// </summary>
		/// <param name="xml">The XML.</param>
		/// <returns></returns>
		public IEnumerable<ISms> ParseToSms(String xml)
		{
			IEnumerable<ISms> allSms = null;

			XElement root = XDocument.Parse(xml).Element("response");

			allSms = CreateSmsEnumerable((XmlDocument)JsonConvert.DeserializeXmlNode((root.Element("json").FirstNode as XCData).Value, "root"));

			HtmlDocument htmlDoc = new HtmlDocument();
			htmlDoc.LoadHtml((root.Element("html").FirstNode as XCData).Value);
			CompletePopulationOfSmsEnumerable(allSms, htmlDoc);

			return allSms;
		}

		#endregion

		#region Private Methods

		#region ISmsXMLParser Helpers

		/// <summary>
		/// Creates the phone.
		/// Sets Kind to InformationKind.Mobile.
		/// </summary>
		/// <param name="phoneNumber">The phone number.</param>
		/// <returns></returns>
		private IPhone CreatePhone(String phoneNumber)
		{
			// Assume that Sms was sent from a Mobile number.
			Phone phone = new Phone { Kind = InformationKind.Mobile };

			if (phoneNumber.StartsWith("+1"))
			{
				phone.Number = phoneNumber.Remove(0, 2);
			}
			else
			{
				phone.Number = phoneNumber;
			}

			return phone;
		}

		/// <summary>
		/// Creates the Sms enumerable.
		/// Only populates Sender and IsRead for each Sms.
		/// Sender is an assumed sender and will need to be verified.
		/// </summary>
		/// <param name="document">The document.</param>
		/// <returns></returns>
		private IEnumerable<ISms> CreateSmsEnumerable(XmlDocument document)
		{
			List<ISms> allSms = new List<ISms>();

			foreach (XmlLinkedNode node in document.FirstChild.ChildNodes[0].ChildNodes)
			{
				Sms sms = new Sms();
				sms.Id = node.Name;
				foreach (XmlLinkedNode childNode in node.ChildNodes)
				{
					switch (childNode.Name)
					{
						case "phoneNumber":
							// Assume the given number is the sender. This will be verified later on.
							sms.Sender = CreatePhone(childNode.FirstChild.Value);
							break;
						case "isRead":
							sms.IsRead = Boolean.Parse(childNode.FirstChild.Value);
							break;
					}
				}
				allSms.Add(sms);
			}
			return allSms;
		}

		/// <summary>
		/// Completes the population of Sms enumerable.
		/// Verifies the Sender of the Sms.
		/// </summary>
		/// <param name="allSms">All Sms.</param>
		/// <param name="htmlDoc">The htmlDoc.</param>
		private void CompletePopulationOfSmsEnumerable(IEnumerable<ISms> allSms, HtmlDocument htmlDoc)
		{
			foreach (Sms sms in allSms)
			{
				IEnumerable<HtmlNode> smsNodeChildren = htmlDoc.GetElementbyId(sms.Id).DescendantNodes();

				sms.Messages = GetMessages(sms, GetNodesWithClassName(smsNodeChildren, "gc-message-sms-row"));
				// GetRecipients relies on sms.Sender being the assumed sender from earlier operations.
				sms.Recipients = GetRecipients(sms.Sender, sms.Messages);
				sms.Sender = sms.Messages.First().Sender;
			}
		}

		/// <summary>
		/// Gets the messages.
		/// </summary>
		/// <param name="sms">The Sms.</param>
		/// <param name="smsMessages">The Sms messages.</param>
		/// <returns></returns>
		private IEnumerable<IMessage> GetMessages(ISms sms, IEnumerable<HtmlNode> smsMessages)
		{
			List<IMessage> messages = new List<IMessage>();

			foreach (HtmlNode node in smsMessages)
			{
				IMessage message = new Message
				{
					Sender = GetNodesWithClassName(node.ChildNodes, "gc-message-sms-from").First().InnerText == userIsOwnerFlag ?
						// Remove whitespace and colon from userIsOwnerFlag.
						CreatePhone(GetUserIsOwnerFlagAsPhoneNumber())
						: sms.Sender,
					Text = GetNodesWithClassName(node.ChildNodes, "gc-message-sms-text").First().InnerText
				};
				messages.Add(message);
			}
			return messages;
		}

		/// <summary>
		/// Gets the nodes with specified class name.
		/// </summary>
		/// <param name="nodes">The nodes.</param>
		/// <param name="className">Name of the class.</param>
		/// <returns></returns>
		private IEnumerable<HtmlNode> GetNodesWithClassName(IEnumerable<HtmlNode> nodes, String className)
		{
			return nodes.Where(new Func<HtmlNode, Boolean>(n => n.Attributes.Where(new Func<HtmlAttribute, Boolean>(at => at.Value == className)).Count() > 0));
		}

		/// <summary>
		/// Gets the recipients.
		/// Compares the first message Sender to the assumedSender.
		///	If they are equal no changes are made
		///	Else 
		/// </summary>
		/// <param name="assumedSender">The assumed sender.</param>
		/// <param name="messages">The messages.</param>
		/// <returns></returns>
		private IEnumerable<IPhone> GetRecipients(IPhone assumedSender, IEnumerable<IMessage> messages)
		{
			List<IPhone> recipients = new List<IPhone>();

			if (assumedSender.Number != messages.First().Sender.Number)
			{
				recipients.Add(assumedSender);
			}
			else
			{
				recipients.Add(CreatePhone(GetUserIsOwnerFlagAsPhoneNumber()));
			}

			return recipients;
		}

		#endregion

		/// <summary>
		/// Gets the userIsOwnerflag as phone number.
		/// </summary>
		/// <returns></returns>
		private static String GetUserIsOwnerFlagAsPhoneNumber()
		{
			return userIsOwnerFlag.Trim().Substring(0, userIsOwnerFlag.Trim().Length - 1);
		}

		#endregion
	}
}
